@atscript/core 0.1.27 → 0.1.28
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/dist/index.cjs +427 -134
- package/dist/index.d.ts +7 -12
- package/dist/index.mjs +427 -134
- package/package.json +9 -3
- package/scripts/setup-skills.js +77 -0
- package/skills/atscript-core/.gitkeep +0 -0
- package/skills/atscript-core/SKILL.md +32 -0
- package/skills/atscript-core/annotations.md +157 -0
- package/skills/atscript-core/core.md +125 -0
- package/skills/atscript-core/plugins.md +206 -0
- package/skills/atscript-core/primitives.md +156 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.28",
|
|
4
4
|
"description": "Core library for Atscript parsing and file generation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"annotations",
|
|
@@ -20,8 +20,13 @@
|
|
|
20
20
|
"url": "git+https://github.com/moostjs/atscript.git",
|
|
21
21
|
"directory": "packages/core"
|
|
22
22
|
},
|
|
23
|
+
"bin": {
|
|
24
|
+
"atscript-core-skill": "./scripts/setup-skills.js"
|
|
25
|
+
},
|
|
23
26
|
"files": [
|
|
24
|
-
"dist"
|
|
27
|
+
"dist",
|
|
28
|
+
"skills",
|
|
29
|
+
"scripts/setup-skills.js"
|
|
25
30
|
],
|
|
26
31
|
"type": "module",
|
|
27
32
|
"main": "dist/index.mjs",
|
|
@@ -46,6 +51,7 @@
|
|
|
46
51
|
},
|
|
47
52
|
"scripts": {
|
|
48
53
|
"pub": "pnpm publish --access public",
|
|
49
|
-
"test": "vitest"
|
|
54
|
+
"test": "vitest",
|
|
55
|
+
"setup-skills": "node ./scripts/setup-skills.js"
|
|
50
56
|
}
|
|
51
57
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* prettier-ignore */
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import os from 'os'
|
|
6
|
+
import { fileURLToPath } from 'url'
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
9
|
+
|
|
10
|
+
const SKILL_NAME = 'atscript-core'
|
|
11
|
+
const SKILL_SRC = path.join(__dirname, '..', 'skills', SKILL_NAME)
|
|
12
|
+
|
|
13
|
+
if (!fs.existsSync(SKILL_SRC)) {
|
|
14
|
+
console.error(`No skills found at ${SKILL_SRC}`)
|
|
15
|
+
console.error('Add your SKILL.md files to the skills/' + SKILL_NAME + '/ directory first.')
|
|
16
|
+
process.exit(1)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const AGENTS = {
|
|
20
|
+
'Claude Code': { dir: '.claude/skills', global: path.join(os.homedir(), '.claude', 'skills') },
|
|
21
|
+
'Cursor': { dir: '.cursor/skills', global: path.join(os.homedir(), '.cursor', 'skills') },
|
|
22
|
+
'Windsurf': { dir: '.windsurf/skills', global: path.join(os.homedir(), '.windsurf', 'skills') },
|
|
23
|
+
'Codex': { dir: '.codex/skills', global: path.join(os.homedir(), '.codex', 'skills') },
|
|
24
|
+
'OpenCode': { dir: '.opencode/skills', global: path.join(os.homedir(), '.opencode', 'skills') },
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const args = process.argv.slice(2)
|
|
28
|
+
const isGlobal = args.includes('--global') || args.includes('-g')
|
|
29
|
+
const isPostinstall = args.includes('--postinstall')
|
|
30
|
+
let installed = 0, skipped = 0
|
|
31
|
+
const installedDirs = []
|
|
32
|
+
|
|
33
|
+
for (const [agentName, cfg] of Object.entries(AGENTS)) {
|
|
34
|
+
const targetBase = isGlobal ? cfg.global : path.join(process.cwd(), cfg.dir)
|
|
35
|
+
const agentRootDir = path.dirname(cfg.global)
|
|
36
|
+
|
|
37
|
+
if (isPostinstall || isGlobal) {
|
|
38
|
+
if (!fs.existsSync(agentRootDir)) { skipped++; continue }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const dest = path.join(targetBase, SKILL_NAME)
|
|
42
|
+
try {
|
|
43
|
+
fs.mkdirSync(dest, { recursive: true })
|
|
44
|
+
fs.cpSync(SKILL_SRC, dest, { recursive: true })
|
|
45
|
+
console.log(`✅ ${agentName}: installed to ${dest}`)
|
|
46
|
+
installed++
|
|
47
|
+
if (!isGlobal) installedDirs.push(cfg.dir + '/' + SKILL_NAME)
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.warn(`⚠️ ${agentName}: failed — ${err.message}`)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Add locally-installed skill dirs to .gitignore
|
|
54
|
+
if (!isGlobal && installedDirs.length > 0) {
|
|
55
|
+
const gitignorePath = path.join(process.cwd(), '.gitignore')
|
|
56
|
+
let gitignoreContent = ''
|
|
57
|
+
try { gitignoreContent = fs.readFileSync(gitignorePath, 'utf8') } catch {}
|
|
58
|
+
const linesToAdd = installedDirs.filter(d => !gitignoreContent.includes(d))
|
|
59
|
+
if (linesToAdd.length > 0) {
|
|
60
|
+
const hasHeader = gitignoreContent.includes('# AI agent skills')
|
|
61
|
+
const block = (gitignoreContent && !gitignoreContent.endsWith('\n') ? '\n' : '')
|
|
62
|
+
+ (hasHeader ? '' : '\n# AI agent skills (auto-generated by setup-skills)\n')
|
|
63
|
+
+ linesToAdd.join('\n') + '\n'
|
|
64
|
+
fs.appendFileSync(gitignorePath, block)
|
|
65
|
+
console.log(`📝 Added ${linesToAdd.length} entries to .gitignore`)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (installed === 0 && isPostinstall) {
|
|
70
|
+
// Silence — no agents present
|
|
71
|
+
} else if (installed === 0 && skipped === Object.keys(AGENTS).length) {
|
|
72
|
+
console.log('No agent directories detected. Try --global or run without it for project-local install.')
|
|
73
|
+
} else if (installed === 0) {
|
|
74
|
+
console.log('Nothing installed. Run without --global to install project-locally.')
|
|
75
|
+
} else {
|
|
76
|
+
console.log(`\n✨ Done! Restart your AI agent to pick up the "${SKILL_NAME}" skill.`)
|
|
77
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: atscript-core
|
|
3
|
+
description: Use when working with @atscript/core — configuring atscript.config.ts with defineConfig, defining custom AnnotationSpec annotations, creating custom TPrimitiveConfig primitives, building TAtscriptPlugin plugins with config/resolve/load/onDocument/render/buildEnd hooks, understanding built-in @meta.* @expect.* @ui.* @db.* @emit.* annotations, or working with AtscriptDoc/AtscriptRepo APIs.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @atscript/core
|
|
7
|
+
|
|
8
|
+
The foundation package for Atscript — a universal type and metadata description language. Provides the parser, AST, annotation system, plugin architecture, and all built-in annotations and primitives that language extensions (TypeScript, Python, etc.) build upon.
|
|
9
|
+
|
|
10
|
+
## How to use this skill
|
|
11
|
+
|
|
12
|
+
Read the domain file that matches the task. Do not load all files — only what you need.
|
|
13
|
+
|
|
14
|
+
| Domain | File | Load when… |
|
|
15
|
+
| ------------------------ | -------------------------------- | --------------------------------------------------------------------------------- |
|
|
16
|
+
| Setup & configuration | [core.md](core.md) | Installing, configuring `atscript.config.ts`, understanding the package structure |
|
|
17
|
+
| Annotations reference | [annotations.md](annotations.md) | Using or defining `@meta.*`, `@expect.*`, `@ui.*`, `@db.*`, `@emit.*` annotations |
|
|
18
|
+
| Primitives reference | [primitives.md](primitives.md) | Using built-in primitives (`string.email`, `number.int`) or defining custom ones |
|
|
19
|
+
| Plugin development | [plugins.md](plugins.md) | Creating plugins with `createAtscriptPlugin`, implementing hooks |
|
|
20
|
+
|
|
21
|
+
## Quick reference
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
// Configuration
|
|
25
|
+
import { defineConfig, AnnotationSpec } from '@atscript/core'
|
|
26
|
+
|
|
27
|
+
// Plugin creation
|
|
28
|
+
import { createAtscriptPlugin } from '@atscript/core'
|
|
29
|
+
|
|
30
|
+
// AST / document types (for plugin authors)
|
|
31
|
+
import { AtscriptDoc, AtscriptRepo } from '@atscript/core'
|
|
32
|
+
```
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Annotations Reference — @atscript/core
|
|
2
|
+
|
|
3
|
+
> Complete reference for all built-in annotations and the `AnnotationSpec` API for defining custom ones.
|
|
4
|
+
|
|
5
|
+
## Built-in Annotation Namespaces
|
|
6
|
+
|
|
7
|
+
### `@meta.*` — Semantic Metadata
|
|
8
|
+
|
|
9
|
+
| Annotation | Arguments | Multiple | NodeType | Description |
|
|
10
|
+
| --------------------- | -------------------- | -------- | ---------------- | ---------------------------------------------------------- |
|
|
11
|
+
| `@meta.label` | `text: string` | no | any | Human-readable label for UI, logs, documentation |
|
|
12
|
+
| `@meta.id` | _(none)_ | no | prop | Mark field as unique identifier; multiple = composite PK |
|
|
13
|
+
| `@meta.description` | `text: string` | no | any | Detailed description |
|
|
14
|
+
| `@meta.documentation` | `text: string` | **yes** | any | Multi-line Markdown docs (each appends a line) |
|
|
15
|
+
| `@meta.sensitive` | _(none)_ | no | prop, type | Sensitive data (passwords, keys) — hidden in logs/UI |
|
|
16
|
+
| `@meta.readonly` | _(none)_ | no | prop, type | Read-only field |
|
|
17
|
+
| `@meta.required` | `message?: string` | no | string, boolean | Required: strings must be non-whitespace; booleans `true` |
|
|
18
|
+
| `@meta.default` | `value: string` | no | prop, type | Default value (strings as-is, others parsed as JSON) |
|
|
19
|
+
| `@meta.example` | `value: string` | no | prop, type | Example value (strings as-is, others parsed as JSON) |
|
|
20
|
+
|
|
21
|
+
### `@expect.*` — Validation Constraints
|
|
22
|
+
|
|
23
|
+
| Annotation | Arguments | Multiple | Applies To | Description |
|
|
24
|
+
| ------------------- | ------------------------------------------------------- | -------- | ------------- | --------------------------- |
|
|
25
|
+
| `@expect.minLength` | `length: number`, `message?: string` | no | string, array | Minimum length |
|
|
26
|
+
| `@expect.maxLength` | `length: number`, `message?: string` | no | string, array | Maximum length |
|
|
27
|
+
| `@expect.min` | `minValue: number`, `message?: string` | no | number | Minimum value |
|
|
28
|
+
| `@expect.max` | `maxValue: number`, `message?: string` | no | number | Maximum value |
|
|
29
|
+
| `@expect.int` | _(none)_ | no | number | Must be integer |
|
|
30
|
+
| `@expect.pattern` | `pattern: string`, `flags?: string`, `message?: string` | **yes** (append) | string | Regex validation |
|
|
31
|
+
| `@expect.array.key` | _(none)_ | no | string, number | Key field in arrays |
|
|
32
|
+
|
|
33
|
+
### `@ui.*` — UI / Presentation Hints
|
|
34
|
+
|
|
35
|
+
| Annotation | Arguments | Multiple | NodeType | Description |
|
|
36
|
+
| ----------------- | ------------------------------ | --------------- | ----------------------- | ---------------------------------------- |
|
|
37
|
+
| `@ui.placeholder` | `text: string` | no | prop, type | Input placeholder text |
|
|
38
|
+
| `@ui.component` | `name: string` | no | prop, type | UI component hint (`"select"`, etc.) |
|
|
39
|
+
| `@ui.hidden` | _(none)_ | no | prop, type, interface | Hide from UI forms/tables |
|
|
40
|
+
| `@ui.group` | `name: string` | no | prop | Group fields into form sections |
|
|
41
|
+
| `@ui.order` | `order: number` | no | prop | Display order (lower = first) |
|
|
42
|
+
| `@ui.width` | `width: string` | no | prop, type | Layout hint (`"half"`, `"full"`, etc.) |
|
|
43
|
+
| `@ui.icon` | `name: string` | no | prop, type, interface | Icon hint |
|
|
44
|
+
| `@ui.hint` | `text: string` | no | prop, type | Help text / tooltip |
|
|
45
|
+
| `@ui.disabled` | _(none)_ | no | prop, type | Non-interactive field |
|
|
46
|
+
| `@ui.type` | `type: string` | no | prop, type | Input type (`"textarea"`, `"password"`) |
|
|
47
|
+
| `@ui.attr` | `key: string`, `value: string` | **yes** (append) | prop, type, interface | Arbitrary HTML/component attribute |
|
|
48
|
+
| `@ui.class` | `names: string` | **yes** (append) | prop, type, interface | CSS class names |
|
|
49
|
+
| `@ui.style` | `css: string` | **yes** (append) | prop, type, interface | Inline CSS styles |
|
|
50
|
+
|
|
51
|
+
### `@db.*` — Database Schema
|
|
52
|
+
|
|
53
|
+
| Annotation | Arguments | Multiple | NodeType | Description |
|
|
54
|
+
| ------------------- | ---------------------------- | --------------- | --------- | ---------------------------------------- |
|
|
55
|
+
| `@db.table` | `name: string \| true` | no | interface | Database table/collection name |
|
|
56
|
+
| `@db.schema` | `name: string` | no | interface | Database schema (PostgreSQL, etc.) |
|
|
57
|
+
| `@db.index.plain` | `name?: string`, `sort?: string` | **yes** (append) | prop | Standard index (shared name = compound) |
|
|
58
|
+
| `@db.index.unique` | `name?: string` | **yes** (append) | prop | Unique constraint index |
|
|
59
|
+
| `@db.index.fulltext`| `name?: string` | **yes** (append) | prop | Fulltext search index |
|
|
60
|
+
| `@db.column` | `name: string` | no | prop | Override database column name |
|
|
61
|
+
| `@db.default.value` | `value: string` | no | prop | Static default value |
|
|
62
|
+
| `@db.default.fn` | `fn: string` | no | prop | Database function for default |
|
|
63
|
+
| `@db.ignore` | _(none)_ | no | prop | Exclude field from database |
|
|
64
|
+
|
|
65
|
+
### `@emit.*` — Build-time Directives
|
|
66
|
+
|
|
67
|
+
| Annotation | NodeType | Description |
|
|
68
|
+
| ------------------ | --------- | ----------------------------------------------- |
|
|
69
|
+
| `@emit.jsonSchema` | interface | Pre-compute and embed JSON Schema at build time |
|
|
70
|
+
|
|
71
|
+
## Custom Annotations
|
|
72
|
+
|
|
73
|
+
Define in `atscript.config.ts` under the `annotations` key:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { defineConfig, AnnotationSpec } from '@atscript/core'
|
|
77
|
+
|
|
78
|
+
export default defineConfig({
|
|
79
|
+
annotations: {
|
|
80
|
+
// Flat: @tag "value"
|
|
81
|
+
tag: new AnnotationSpec({
|
|
82
|
+
multiple: true,
|
|
83
|
+
mergeStrategy: 'append',
|
|
84
|
+
argument: { name: 'value', type: 'string' },
|
|
85
|
+
description: 'Tag for categorization',
|
|
86
|
+
}),
|
|
87
|
+
|
|
88
|
+
// Namespaced: @api.deprecated "Use v2 instead"
|
|
89
|
+
api: {
|
|
90
|
+
deprecated: new AnnotationSpec({
|
|
91
|
+
argument: { name: 'message', type: 'string', optional: true },
|
|
92
|
+
nodeType: ['prop', 'interface'],
|
|
93
|
+
description: 'Mark as deprecated',
|
|
94
|
+
}),
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
})
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### `AnnotationSpec` Options
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
new AnnotationSpec({
|
|
104
|
+
description?: string, // IntelliSense hover text
|
|
105
|
+
nodeType?: ('interface' | 'type' | 'prop')[], // Where it can be applied
|
|
106
|
+
defType?: string[], // Restrict to value types: 'string', 'number', etc.
|
|
107
|
+
|
|
108
|
+
// Arguments
|
|
109
|
+
argument?: TAnnotationArgument | TAnnotationArgument[],
|
|
110
|
+
// Each: { name, type: 'string'|'number'|'boolean', optional?, description?, values? }
|
|
111
|
+
|
|
112
|
+
// Multiplicity
|
|
113
|
+
multiple?: boolean, // Can appear more than once (default: false)
|
|
114
|
+
mergeStrategy?: 'replace' | 'append', // How values merge on inheritance (default: 'replace')
|
|
115
|
+
|
|
116
|
+
// Advanced
|
|
117
|
+
validate?: (mainToken, args, doc) => TMessages | undefined, // Custom validation
|
|
118
|
+
modify?: (mainToken, args, doc) => void, // AST modification after parsing
|
|
119
|
+
})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### How Annotations Map to Runtime Values
|
|
123
|
+
|
|
124
|
+
| Config | Metadata value type |
|
|
125
|
+
| ------------------------------- | ---------------------------- |
|
|
126
|
+
| No arguments | `true` (boolean flag) |
|
|
127
|
+
| Single argument | The argument value directly |
|
|
128
|
+
| Multiple named arguments | `{ name1: val1, name2: val2 }` |
|
|
129
|
+
| `multiple: true` | Array of the above |
|
|
130
|
+
| `multiple: true` + `append` | Concatenated array on merge |
|
|
131
|
+
|
|
132
|
+
### Merge Strategies
|
|
133
|
+
|
|
134
|
+
When annotations inherit (type → prop, ad-hoc annotate blocks):
|
|
135
|
+
|
|
136
|
+
- **replace** (default) — higher-priority annotation replaces lower entirely
|
|
137
|
+
- **append** — values from both sides concatenate into a single array
|
|
138
|
+
|
|
139
|
+
## Annotation Resolution
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { resolveAnnotation } from '@atscript/core'
|
|
143
|
+
|
|
144
|
+
// Looks up 'ui.placeholder' in the annotation tree
|
|
145
|
+
const spec = resolveAnnotation('ui.placeholder', config.annotations)
|
|
146
|
+
// Returns AnnotationSpec or undefined
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The function splits the dotted name and walks the `TAnnotationsTree` hierarchy.
|
|
150
|
+
|
|
151
|
+
## Best Practices
|
|
152
|
+
|
|
153
|
+
- Use namespaced annotations (`@ns.name`) to avoid collisions
|
|
154
|
+
- Set `nodeType` to catch misuse early (e.g., `@db.table` only on interfaces)
|
|
155
|
+
- Use `mergeStrategy: 'append'` for annotations that accumulate (patterns, tags, styles)
|
|
156
|
+
- Use `validate` for complex cross-field checks (e.g., `@db.ignore` conflicts with `@meta.id`)
|
|
157
|
+
- Keep `description` concise — it shows in IDE hover tooltips
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Setup & Configuration — @atscript/core
|
|
2
|
+
|
|
3
|
+
> How to install, configure, and understand the @atscript/core package.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`@atscript/core` is the foundation of the Atscript ecosystem. It provides:
|
|
8
|
+
- Parser for `.as` files → AST
|
|
9
|
+
- Annotation system (`AnnotationSpec`, annotation trees)
|
|
10
|
+
- Primitive type system
|
|
11
|
+
- Plugin architecture
|
|
12
|
+
- Built-in annotations (`@meta.*`, `@expect.*`, `@ui.*`, `@db.*`, `@emit.*`)
|
|
13
|
+
- Built-in primitives (`string`, `number`, `boolean`, `null`, `void`, `phantom` + extensions)
|
|
14
|
+
|
|
15
|
+
Language extensions like `@atscript/typescript` build on top of core.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @atscript/core
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Configuration
|
|
24
|
+
|
|
25
|
+
Create `atscript.config.ts` (or `.js`) at your project root:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { defineConfig } from '@atscript/core'
|
|
29
|
+
import tsPlugin from '@atscript/typescript'
|
|
30
|
+
|
|
31
|
+
export default defineConfig({
|
|
32
|
+
rootDir: 'src',
|
|
33
|
+
plugins: [tsPlugin()],
|
|
34
|
+
|
|
35
|
+
// Optional: custom annotations
|
|
36
|
+
annotations: {
|
|
37
|
+
myNamespace: {
|
|
38
|
+
myAnnotation: new AnnotationSpec({ ... }),
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
// Optional: custom primitives
|
|
43
|
+
primitives: {
|
|
44
|
+
currency: { type: 'string', annotations: { 'expect.pattern': { pattern: '^\\d+\\.\\d{2}$' } } },
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// Optional: how unknown annotations are handled
|
|
48
|
+
unknownAnnotation: 'error', // 'error' (default) | 'warn' | 'allow'
|
|
49
|
+
|
|
50
|
+
// Optional: file patterns
|
|
51
|
+
include: ['**/*.as'],
|
|
52
|
+
exclude: ['**/node_modules/**'],
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### `TAtscriptConfig` Fields
|
|
57
|
+
|
|
58
|
+
| Field | Type | Description |
|
|
59
|
+
| ------------------- | ----------------------------- | ---------------------------------------------- |
|
|
60
|
+
| `rootDir` | `string` | Root directory for `.as` files |
|
|
61
|
+
| `entries` | `string[]` | Explicit entry files (instead of auto-discover) |
|
|
62
|
+
| `plugins` | `TAtscriptPlugin[]` | Plugins to load (order matters) |
|
|
63
|
+
| `annotations` | `TAnnotationsTree` | Custom annotation definitions |
|
|
64
|
+
| `primitives` | `Record<string, TPrimitiveConfig>` | Custom primitive type definitions |
|
|
65
|
+
| `unknownAnnotation` | `'error' \| 'warn' \| 'allow'` | How to handle unrecognized annotations |
|
|
66
|
+
| `include` | `string[]` | Glob patterns to include |
|
|
67
|
+
| `exclude` | `string[]` | Glob patterns to exclude |
|
|
68
|
+
| `format` | `string` | Output format (set by CLI or build tool) |
|
|
69
|
+
| `outDir` | `string` | Output directory |
|
|
70
|
+
|
|
71
|
+
## Package Exports
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
// Configuration
|
|
75
|
+
export { defineConfig } from '@atscript/core'
|
|
76
|
+
|
|
77
|
+
// Annotations
|
|
78
|
+
export { AnnotationSpec, isAnnotationSpec, resolveAnnotation } from '@atscript/core'
|
|
79
|
+
|
|
80
|
+
// Config types
|
|
81
|
+
export type { TAtscriptConfig, TAnnotationsTree } from '@atscript/core'
|
|
82
|
+
|
|
83
|
+
// Plugin system
|
|
84
|
+
export { createAtscriptPlugin } from '@atscript/core'
|
|
85
|
+
export type { TAtscriptPlugin, TPluginOutput } from '@atscript/core'
|
|
86
|
+
export { DEFAULT_FORMAT } from '@atscript/core'
|
|
87
|
+
|
|
88
|
+
// Document & Repo (for plugin authors)
|
|
89
|
+
export { AtscriptDoc } from '@atscript/core'
|
|
90
|
+
export { AtscriptRepo } from '@atscript/core'
|
|
91
|
+
|
|
92
|
+
// Parser nodes (for plugin authors)
|
|
93
|
+
export { /* node type guards and types */ } from '@atscript/core'
|
|
94
|
+
|
|
95
|
+
// Build utilities
|
|
96
|
+
export { build } from '@atscript/core'
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Architecture
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
atscript.config.ts
|
|
103
|
+
└─ defineConfig({ plugins, annotations, primitives })
|
|
104
|
+
└─ Plugins merge configs via defu (deep defaults)
|
|
105
|
+
└─ AtscriptRepo orchestrates parsing
|
|
106
|
+
└─ AtscriptDoc per .as file (AST + metadata)
|
|
107
|
+
└─ Plugins render output (render hook)
|
|
108
|
+
└─ Plugins aggregate (buildEnd hook)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Plugins execute in array order. Each plugin's `config()` output is merged using `defu`, so multiple plugins can contribute annotations and primitives without conflicts.
|
|
112
|
+
|
|
113
|
+
## Annotation Namespaces (Built-in)
|
|
114
|
+
|
|
115
|
+
The core ships five annotation namespaces:
|
|
116
|
+
|
|
117
|
+
| Namespace | Purpose | Example |
|
|
118
|
+
| ---------- | ---------------------------------- | -------------------------- |
|
|
119
|
+
| `meta.*` | Semantic metadata | `@meta.label "Name"` |
|
|
120
|
+
| `expect.*` | Validation constraints | `@expect.min 0` |
|
|
121
|
+
| `ui.*` | Presentation / UI hints | `@ui.placeholder "Enter…"` |
|
|
122
|
+
| `db.*` | Database schema | `@db.table "users"` |
|
|
123
|
+
| `emit.*` | Build-time directives | `@emit.jsonSchema` |
|
|
124
|
+
|
|
125
|
+
See [annotations.md](annotations.md) for the full reference.
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Plugin Development — @atscript/core
|
|
2
|
+
|
|
3
|
+
> How to create Atscript plugins that add annotations, primitives, and code generators.
|
|
4
|
+
|
|
5
|
+
## Concepts
|
|
6
|
+
|
|
7
|
+
A plugin is a plain object implementing `TAtscriptPlugin` — a `name` plus optional hooks. Plugins extend Atscript by adding annotations, primitives, and output generators without modifying the parser.
|
|
8
|
+
|
|
9
|
+
## Creating a Plugin
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { createAtscriptPlugin, AnnotationSpec } from '@atscript/core'
|
|
13
|
+
|
|
14
|
+
export const myPlugin = () =>
|
|
15
|
+
createAtscriptPlugin({
|
|
16
|
+
name: 'my-plugin',
|
|
17
|
+
|
|
18
|
+
// Add annotations and primitives
|
|
19
|
+
config(config) {
|
|
20
|
+
return {
|
|
21
|
+
annotations: {
|
|
22
|
+
api: {
|
|
23
|
+
deprecated: new AnnotationSpec({
|
|
24
|
+
description: 'Mark as deprecated',
|
|
25
|
+
argument: { name: 'message', type: 'string', optional: true },
|
|
26
|
+
}),
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
// Generate output files
|
|
33
|
+
render(doc, format) {
|
|
34
|
+
if (format === 'json') {
|
|
35
|
+
return [{ fileName: `${doc.name}.json`, content: JSON.stringify(doc.metadata) }]
|
|
36
|
+
}
|
|
37
|
+
return []
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`createAtscriptPlugin` is a type-safe identity function — returns the object as-is but provides IntelliSense on hook signatures.
|
|
43
|
+
|
|
44
|
+
## Registering Plugins
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { defineConfig } from '@atscript/core'
|
|
48
|
+
import { myPlugin } from './plugins/my-plugin'
|
|
49
|
+
|
|
50
|
+
export default defineConfig({
|
|
51
|
+
rootDir: 'src',
|
|
52
|
+
plugins: [myPlugin()],
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Plugins execute in array order. Each plugin's `config()` output is merged with accumulated config using `defu` (deep defaults).
|
|
57
|
+
|
|
58
|
+
## The TAtscriptPlugin Interface
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
interface TAtscriptPlugin {
|
|
62
|
+
name: string
|
|
63
|
+
config?(config: TAtscriptConfig): TAtscriptConfig | undefined
|
|
64
|
+
resolve?(id: string): string | undefined
|
|
65
|
+
load?(id: string): string | undefined
|
|
66
|
+
onDocument?(doc: AtscriptDoc): void
|
|
67
|
+
render?(doc: AtscriptDoc, format: string): TPluginOutput[]
|
|
68
|
+
buildEnd?(output: TOutput[], format: string, repo: AtscriptRepo): void
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
All hooks except `name` are optional. Hooks can be sync or async.
|
|
73
|
+
|
|
74
|
+
## Hook Reference
|
|
75
|
+
|
|
76
|
+
### `config(config)`
|
|
77
|
+
|
|
78
|
+
Called once during initialization. Return additional config to merge (annotations, primitives). Multiple plugins merge via `defu`.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
config(config) {
|
|
82
|
+
return {
|
|
83
|
+
primitives: {
|
|
84
|
+
url: { type: 'string', annotations: { 'expect.pattern': { pattern: '^https?://' } } },
|
|
85
|
+
},
|
|
86
|
+
annotations: {
|
|
87
|
+
cache: { ttl: new AnnotationSpec({ argument: { name: 'seconds', type: 'number' } }) },
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### `resolve(id)`
|
|
94
|
+
|
|
95
|
+
Remap or virtualize module paths. Return a new path string or `undefined` to skip.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
resolve(id) {
|
|
99
|
+
if (id === '@my-lib/types') return '/path/to/virtual-types.as'
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### `load(id)`
|
|
104
|
+
|
|
105
|
+
Provide virtual file content. Return `.as` source string or `undefined`.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
load(id) {
|
|
109
|
+
if (id === '/virtual/base-entity.as') {
|
|
110
|
+
return `export interface BaseEntity { id: string.uuid }`
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### `onDocument(doc)`
|
|
116
|
+
|
|
117
|
+
Post-process a parsed document. Access the AST, inject virtual props, run checks.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
onDocument(doc) {
|
|
121
|
+
// Access parsed interfaces
|
|
122
|
+
for (const [name, node] of doc.interfaces) {
|
|
123
|
+
// Inspect or modify the AST
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### `render(doc, format)`
|
|
129
|
+
|
|
130
|
+
Generate output files for each document. Return `TPluginOutput[]`.
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
render(doc, format) {
|
|
134
|
+
if (format === 'dts' || format === DEFAULT_FORMAT) {
|
|
135
|
+
return [{ fileName: `${doc.name}.d.ts`, content: generateTypes(doc) }]
|
|
136
|
+
}
|
|
137
|
+
return []
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The `DEFAULT_FORMAT` constant is the format used when saving `.as` files in an editor. Check for it alongside your plugin's format strings.
|
|
142
|
+
|
|
143
|
+
### `buildEnd(output, format, repo)`
|
|
144
|
+
|
|
145
|
+
Called after all documents are rendered. Aggregate across all docs — useful for global type declarations, index files, etc.
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
buildEnd(output, format, repo) {
|
|
149
|
+
// repo.getUsedAnnotations() — all annotation specs registered
|
|
150
|
+
// output — array of { doc, files } from render phase
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## AtscriptDoc API (Key Methods)
|
|
155
|
+
|
|
156
|
+
| Method / Property | Description |
|
|
157
|
+
| ----------------------- | ------------------------------------------------ |
|
|
158
|
+
| `doc.name` | File name (without extension) |
|
|
159
|
+
| `doc.path` | Full file path |
|
|
160
|
+
| `doc.interfaces` | Map of interface names → AST nodes |
|
|
161
|
+
| `doc.types` | Map of type names → AST nodes |
|
|
162
|
+
| `doc.imports` | Import declarations |
|
|
163
|
+
| `doc.exports` | Export declarations |
|
|
164
|
+
| `doc.unwindType(id, chain)` | Resolve a type reference to its definition |
|
|
165
|
+
|
|
166
|
+
## AtscriptRepo API (Key Methods)
|
|
167
|
+
|
|
168
|
+
| Method / Property | Description |
|
|
169
|
+
| ------------------------------ | ---------------------------------------------- |
|
|
170
|
+
| `repo.getUsedAnnotations()` | Iterator of all registered annotation specs |
|
|
171
|
+
| `repo.documents` | Map of all parsed documents |
|
|
172
|
+
| `repo.config` | Merged configuration |
|
|
173
|
+
|
|
174
|
+
## Common Patterns
|
|
175
|
+
|
|
176
|
+
### Annotation-only plugin
|
|
177
|
+
|
|
178
|
+
Just `config()` — the simplest plugin type:
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
export const tagsPlugin = () =>
|
|
182
|
+
createAtscriptPlugin({
|
|
183
|
+
name: 'tags',
|
|
184
|
+
config: () => ({
|
|
185
|
+
annotations: {
|
|
186
|
+
tag: new AnnotationSpec({
|
|
187
|
+
multiple: true,
|
|
188
|
+
mergeStrategy: 'append',
|
|
189
|
+
argument: { name: 'value', type: 'string' },
|
|
190
|
+
}),
|
|
191
|
+
},
|
|
192
|
+
}),
|
|
193
|
+
})
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Full language extension
|
|
197
|
+
|
|
198
|
+
`config()` + `render()` + `buildEnd()` — see `@atscript/typescript` for the reference implementation.
|
|
199
|
+
|
|
200
|
+
## Best Practices
|
|
201
|
+
|
|
202
|
+
- Keep plugin names unique and descriptive
|
|
203
|
+
- Use `config()` for annotations/primitives, not hardcoded checks
|
|
204
|
+
- Check `format` in `render()` — plugins may be called with different formats
|
|
205
|
+
- Use `DEFAULT_FORMAT` for output that's essential to the dev experience (e.g., type declarations)
|
|
206
|
+
- Return empty array from `render()` when format doesn't match
|