@notegen/plugin-cli 0.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.
- package/LICENSE +21 -0
- package/README.md +349 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +3 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.js +398 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +13 -0
- package/dist/lib/archive.d.ts +17 -0
- package/dist/lib/archive.js +257 -0
- package/dist/lib/constants.d.ts +16 -0
- package/dist/lib/constants.js +16 -0
- package/dist/lib/diagnostics.d.ts +25 -0
- package/dist/lib/diagnostics.js +49 -0
- package/dist/lib/files.d.ts +30 -0
- package/dist/lib/files.js +396 -0
- package/dist/lib/integrity.d.ts +20 -0
- package/dist/lib/integrity.js +162 -0
- package/dist/lib/manifest.d.ts +13 -0
- package/dist/lib/manifest.js +645 -0
- package/dist/lib/package.d.ts +21 -0
- package/dist/lib/package.js +229 -0
- package/dist/lib/path-rules.d.ts +23 -0
- package/dist/lib/path-rules.js +153 -0
- package/dist/lib/project.d.ts +21 -0
- package/dist/lib/project.js +209 -0
- package/dist/lib/scaffold.d.ts +22 -0
- package/dist/lib/scaffold.js +230 -0
- package/dist/lib/signing.d.ts +26 -0
- package/dist/lib/signing.js +181 -0
- package/dist/lib/strict-json.d.ts +26 -0
- package/dist/lib/strict-json.js +210 -0
- package/dist/lib/tasks.d.ts +70 -0
- package/dist/lib/tasks.js +241 -0
- package/dist/lib/watch.d.ts +6 -0
- package/dist/lib/watch.js +63 -0
- package/package.json +64 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { PluginManifestV1 } from '@notegen/plugin-api';
|
|
2
|
+
import { type BuildPluginProjectOptions } from './project.js';
|
|
3
|
+
export interface ValidateTargetOptions {
|
|
4
|
+
readonly target?: string;
|
|
5
|
+
readonly apiVersion?: string;
|
|
6
|
+
readonly appVersion?: string;
|
|
7
|
+
readonly publicKeyPath?: string;
|
|
8
|
+
readonly requireSignature?: boolean;
|
|
9
|
+
/** Treat a directory as a complete package and reject every undeclared entry. */
|
|
10
|
+
readonly completeDirectory?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface ValidationResult {
|
|
13
|
+
readonly kind: 'project' | 'directory' | 'archive';
|
|
14
|
+
readonly target: string;
|
|
15
|
+
readonly manifest: PluginManifestV1;
|
|
16
|
+
readonly signed: boolean;
|
|
17
|
+
readonly signatureVerified: boolean;
|
|
18
|
+
readonly appCompatibilityChecked: boolean;
|
|
19
|
+
readonly appCompatibilityNote: string;
|
|
20
|
+
readonly archiveSha256?: string;
|
|
21
|
+
readonly archiveSize?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface PackPluginOptions extends BuildPluginProjectOptions {
|
|
24
|
+
readonly output?: string;
|
|
25
|
+
readonly force?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface PackPluginResult {
|
|
28
|
+
readonly path: string;
|
|
29
|
+
readonly pluginId: string;
|
|
30
|
+
readonly version: string;
|
|
31
|
+
readonly sha256: string;
|
|
32
|
+
readonly size: number;
|
|
33
|
+
readonly developmentDirectory: string;
|
|
34
|
+
}
|
|
35
|
+
export interface GenerateKeysOptions {
|
|
36
|
+
readonly directory?: string;
|
|
37
|
+
readonly privateKeyPath?: string;
|
|
38
|
+
readonly publicKeyPath?: string;
|
|
39
|
+
readonly passphrase?: string | Uint8Array;
|
|
40
|
+
readonly force?: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface GeneratedKeysResult {
|
|
43
|
+
readonly privateKeyPath: string;
|
|
44
|
+
readonly publicKeyPath: string;
|
|
45
|
+
readonly keyId: string;
|
|
46
|
+
readonly publicKey: string;
|
|
47
|
+
}
|
|
48
|
+
export interface SignPluginOptions {
|
|
49
|
+
readonly archive: string;
|
|
50
|
+
readonly privateKeyPath: string;
|
|
51
|
+
readonly output?: string;
|
|
52
|
+
readonly passphrase?: string | Uint8Array;
|
|
53
|
+
readonly force?: boolean;
|
|
54
|
+
readonly apiVersion?: string;
|
|
55
|
+
readonly appVersion?: string;
|
|
56
|
+
}
|
|
57
|
+
export interface SignPluginResult {
|
|
58
|
+
readonly path: string;
|
|
59
|
+
readonly pluginId: string;
|
|
60
|
+
readonly version: string;
|
|
61
|
+
readonly keyId: string;
|
|
62
|
+
readonly publicKey: string;
|
|
63
|
+
readonly sha256: string;
|
|
64
|
+
readonly size: number;
|
|
65
|
+
}
|
|
66
|
+
export declare function readPublisherPublicKey(path: string): Promise<string>;
|
|
67
|
+
export declare function validatePluginTarget(options?: ValidateTargetOptions): Promise<ValidationResult>;
|
|
68
|
+
export declare function packPluginProject(options?: PackPluginOptions): Promise<PackPluginResult>;
|
|
69
|
+
export declare function generatePublisherKeys(options?: GenerateKeysOptions): Promise<GeneratedKeysResult>;
|
|
70
|
+
export declare function signPluginArchive(options: SignPluginOptions): Promise<SignPluginResult>;
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { basename, join, resolve } from 'node:path';
|
|
2
|
+
import { lstat, readFile } from 'node:fs/promises';
|
|
3
|
+
import { PACKAGE_EXTENSION, RELEASE_OUTPUT_DIRECTORY, UNSIGNED_PACKAGE_EXTENSION, } from './constants.js';
|
|
4
|
+
import { readPackageArchive, writePackageArchive } from './archive.js';
|
|
5
|
+
import { diagnostic, DiagnosticError, fail } from './diagnostics.js';
|
|
6
|
+
import { atomicWriteFiles, assertRegularFile, pathExists } from './files.js';
|
|
7
|
+
import { readCompletePackageDirectory, readPackageDirectory, validatePackageFiles, } from './package.js';
|
|
8
|
+
import { buildPluginProject, validatePluginProjectSource, } from './project.js';
|
|
9
|
+
import { generatePublisherKeyPair, publisherKeyId, publisherPublicKeyFromPrivate, signPackage, } from './signing.js';
|
|
10
|
+
import { isJsonObject, parseStrictJson } from './strict-json.js';
|
|
11
|
+
function publicKeyDocument(keyId, publicKey) {
|
|
12
|
+
return Object.freeze({ algorithm: 'Ed25519', keyId, publicKey });
|
|
13
|
+
}
|
|
14
|
+
async function directoryEntryExists(path, label) {
|
|
15
|
+
try {
|
|
16
|
+
await lstat(path);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
const cause = error;
|
|
21
|
+
if (cause.code === 'ENOENT')
|
|
22
|
+
return false;
|
|
23
|
+
throw new DiagnosticError(diagnostic({
|
|
24
|
+
code: 'target.inspection-failed',
|
|
25
|
+
message: `Unable to inspect ${label}`,
|
|
26
|
+
path,
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export async function readPublisherPublicKey(path) {
|
|
31
|
+
const target = resolve(path);
|
|
32
|
+
await assertRegularFile(target, 'Publisher public key');
|
|
33
|
+
const bytes = await readFile(target);
|
|
34
|
+
let text;
|
|
35
|
+
try {
|
|
36
|
+
text = new TextDecoder('utf-8', { fatal: true }).decode(bytes).trim();
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
fail('key.invalid-encoding', 'Publisher public key file must be UTF-8', target);
|
|
40
|
+
}
|
|
41
|
+
if (text.startsWith('{')) {
|
|
42
|
+
const value = parseStrictJson(text, 'publisher public key');
|
|
43
|
+
if (!isJsonObject(value))
|
|
44
|
+
fail('key.invalid-public-document', 'Publisher public key must be an object', target);
|
|
45
|
+
const unknown = Object.keys(value).find((key) => !['algorithm', 'keyId', 'publicKey'].includes(key));
|
|
46
|
+
if (unknown !== undefined) {
|
|
47
|
+
fail('key.unknown-field', `Publisher public key contains unknown field ${unknown}`, target);
|
|
48
|
+
}
|
|
49
|
+
if (value.algorithm !== 'Ed25519' || typeof value.keyId !== 'string' || typeof value.publicKey !== 'string') {
|
|
50
|
+
fail('key.invalid-public-document', 'Publisher public key document is incomplete or unsupported', target);
|
|
51
|
+
}
|
|
52
|
+
if (publisherKeyId(value.publicKey) !== value.keyId) {
|
|
53
|
+
fail('key.id-mismatch', 'Publisher public-key keyId does not match its key material', target);
|
|
54
|
+
}
|
|
55
|
+
return value.publicKey;
|
|
56
|
+
}
|
|
57
|
+
return text;
|
|
58
|
+
}
|
|
59
|
+
export async function validatePluginTarget(options = {}) {
|
|
60
|
+
const target = resolve(options.target ?? process.cwd());
|
|
61
|
+
let metadata;
|
|
62
|
+
try {
|
|
63
|
+
metadata = await lstat(target);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
const cause = error;
|
|
67
|
+
if (cause.code !== 'ENOENT') {
|
|
68
|
+
throw new DiagnosticError(diagnostic({
|
|
69
|
+
code: 'target.inspection-failed',
|
|
70
|
+
message: 'Unable to inspect the validation target',
|
|
71
|
+
path: target,
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
throw new DiagnosticError(diagnostic({
|
|
75
|
+
code: 'target.missing',
|
|
76
|
+
message: 'Validation target does not exist',
|
|
77
|
+
path: target,
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
if (metadata.isSymbolicLink()) {
|
|
81
|
+
fail('target.symlink', 'Validation target cannot be a symbolic link', target);
|
|
82
|
+
}
|
|
83
|
+
const publicKey = options.publicKeyPath === undefined
|
|
84
|
+
? undefined
|
|
85
|
+
: await readPublisherPublicKey(options.publicKeyPath);
|
|
86
|
+
const appCompatibilityChecked = options.appVersion !== undefined;
|
|
87
|
+
const appCompatibilityNote = appCompatibilityChecked
|
|
88
|
+
? `Compatibility checked against NoteGen ${options.appVersion}.`
|
|
89
|
+
: 'NoteGen app compatibility was not checked; pass --app-version to check minAppVersion.';
|
|
90
|
+
if (metadata.isDirectory()) {
|
|
91
|
+
if (!await directoryEntryExists(join(target, 'integrity.json'), 'integrity.json')) {
|
|
92
|
+
const project = await validatePluginProjectSource({
|
|
93
|
+
directory: target,
|
|
94
|
+
...(options.apiVersion === undefined ? {} : { apiVersion: options.apiVersion }),
|
|
95
|
+
...(options.appVersion === undefined ? {} : { appVersion: options.appVersion }),
|
|
96
|
+
});
|
|
97
|
+
if (publicKey !== undefined || options.requireSignature) {
|
|
98
|
+
fail('signature.not-applicable', 'A source project has no package signature; build it before verifying a signature', target);
|
|
99
|
+
}
|
|
100
|
+
return Object.freeze({
|
|
101
|
+
kind: 'project',
|
|
102
|
+
target,
|
|
103
|
+
manifest: project.manifest,
|
|
104
|
+
signed: false,
|
|
105
|
+
signatureVerified: false,
|
|
106
|
+
appCompatibilityChecked,
|
|
107
|
+
appCompatibilityNote,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const files = options.completeDirectory
|
|
111
|
+
? await readCompletePackageDirectory(target)
|
|
112
|
+
: await readPackageDirectory(target);
|
|
113
|
+
const validated = validatePackageFiles(files, {
|
|
114
|
+
...(options.apiVersion === undefined ? {} : { apiVersion: options.apiVersion }),
|
|
115
|
+
...(options.appVersion === undefined ? {} : { appVersion: options.appVersion }),
|
|
116
|
+
...(publicKey === undefined ? {} : { publicKey }),
|
|
117
|
+
...(options.requireSignature === undefined ? {} : { requireSignature: options.requireSignature }),
|
|
118
|
+
});
|
|
119
|
+
return Object.freeze({
|
|
120
|
+
kind: 'directory',
|
|
121
|
+
target,
|
|
122
|
+
manifest: validated.manifest,
|
|
123
|
+
signed: validated.signature !== undefined,
|
|
124
|
+
signatureVerified: validated.signatureVerified,
|
|
125
|
+
appCompatibilityChecked,
|
|
126
|
+
appCompatibilityNote,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (!metadata.isFile())
|
|
130
|
+
fail('target.invalid-type', 'Validation target must be a file or directory', target);
|
|
131
|
+
if (!basename(target).endsWith(PACKAGE_EXTENSION)) {
|
|
132
|
+
fail('target.invalid-extension', `Plugin archives must end with ${PACKAGE_EXTENSION}`, target);
|
|
133
|
+
}
|
|
134
|
+
const archive = await readPackageArchive(target);
|
|
135
|
+
const finalPackage = !target.endsWith(UNSIGNED_PACKAGE_EXTENSION);
|
|
136
|
+
const validated = validatePackageFiles(archive.files, {
|
|
137
|
+
...(options.apiVersion === undefined ? {} : { apiVersion: options.apiVersion }),
|
|
138
|
+
...(options.appVersion === undefined ? {} : { appVersion: options.appVersion }),
|
|
139
|
+
...(publicKey === undefined ? {} : { publicKey }),
|
|
140
|
+
requireSignature: finalPackage || options.requireSignature === true,
|
|
141
|
+
});
|
|
142
|
+
if (!finalPackage && validated.signature !== undefined) {
|
|
143
|
+
fail('signature.unexpected', `An archive ending with ${UNSIGNED_PACKAGE_EXTENSION} must not contain signature.sig`, target);
|
|
144
|
+
}
|
|
145
|
+
return Object.freeze({
|
|
146
|
+
kind: 'archive',
|
|
147
|
+
target,
|
|
148
|
+
manifest: validated.manifest,
|
|
149
|
+
signed: validated.signature !== undefined,
|
|
150
|
+
signatureVerified: validated.signatureVerified,
|
|
151
|
+
appCompatibilityChecked,
|
|
152
|
+
appCompatibilityNote,
|
|
153
|
+
archiveSha256: archive.sha256,
|
|
154
|
+
archiveSize: archive.size,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
export async function packPluginProject(options = {}) {
|
|
158
|
+
const built = await buildPluginProject(options);
|
|
159
|
+
const output = resolve(options.output ?? join(built.projectDirectory, RELEASE_OUTPUT_DIRECTORY, `${built.manifest.id}-${built.manifest.version}${UNSIGNED_PACKAGE_EXTENSION}`));
|
|
160
|
+
if (!output.endsWith(UNSIGNED_PACKAGE_EXTENSION)) {
|
|
161
|
+
fail('pack.unsigned-extension', `Unsigned packages must end with ${UNSIGNED_PACKAGE_EXTENSION}`, output);
|
|
162
|
+
}
|
|
163
|
+
const archive = await writePackageArchive([...built.package.files].map(([path, bytes]) => ({ path, bytes })), output, { force: options.force });
|
|
164
|
+
return Object.freeze({
|
|
165
|
+
...archive,
|
|
166
|
+
pluginId: built.manifest.id,
|
|
167
|
+
version: built.manifest.version,
|
|
168
|
+
developmentDirectory: built.outputDirectory,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
export async function generatePublisherKeys(options = {}) {
|
|
172
|
+
const directory = resolve(options.directory ?? join(process.cwd(), '.notegen/keys'));
|
|
173
|
+
const privateKeyPath = resolve(options.privateKeyPath ?? join(directory, 'publisher-private.pem'));
|
|
174
|
+
const publicKeyPath = resolve(options.publicKeyPath ?? join(directory, 'publisher-public.json'));
|
|
175
|
+
if (privateKeyPath === publicKeyPath)
|
|
176
|
+
fail('key.same-output', 'Private and public key paths must differ', privateKeyPath);
|
|
177
|
+
if (!options.force) {
|
|
178
|
+
for (const path of [privateKeyPath, publicKeyPath]) {
|
|
179
|
+
if (await pathExists(path)) {
|
|
180
|
+
fail('key.output-exists', 'Refusing to overwrite an existing publisher key', path, 'Use a new location, or pass --force only after backing up the existing key.');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const generated = generatePublisherKeyPair(options.passphrase);
|
|
185
|
+
await atomicWriteFiles([
|
|
186
|
+
{
|
|
187
|
+
path: privateKeyPath,
|
|
188
|
+
contents: generated.privateKeyPem,
|
|
189
|
+
mode: 0o600,
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
path: publicKeyPath,
|
|
193
|
+
contents: `${JSON.stringify(publicKeyDocument(generated.keyId, generated.publicKey), null, 2)}\n`,
|
|
194
|
+
mode: 0o644,
|
|
195
|
+
},
|
|
196
|
+
], { force: options.force });
|
|
197
|
+
return Object.freeze({
|
|
198
|
+
privateKeyPath,
|
|
199
|
+
publicKeyPath,
|
|
200
|
+
keyId: generated.keyId,
|
|
201
|
+
publicKey: generated.publicKey,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
export async function signPluginArchive(options) {
|
|
205
|
+
const input = resolve(options.archive);
|
|
206
|
+
if (!input.endsWith(UNSIGNED_PACKAGE_EXTENSION)) {
|
|
207
|
+
fail('sign.unsigned-extension', `Signer input must end with ${UNSIGNED_PACKAGE_EXTENSION}`, input, 'Run notegen-plugin pack first; the signer never compiles source code.');
|
|
208
|
+
}
|
|
209
|
+
const archive = await readPackageArchive(input);
|
|
210
|
+
if (archive.files.has('signature.sig')) {
|
|
211
|
+
fail('sign.already-signed', 'Unsigned package already contains signature.sig', input);
|
|
212
|
+
}
|
|
213
|
+
const compatibility = {
|
|
214
|
+
...(options.apiVersion === undefined ? {} : { apiVersion: options.apiVersion }),
|
|
215
|
+
...(options.appVersion === undefined ? {} : { appVersion: options.appVersion }),
|
|
216
|
+
};
|
|
217
|
+
const validated = validatePackageFiles(archive.files, compatibility);
|
|
218
|
+
const privateKeyPath = resolve(options.privateKeyPath);
|
|
219
|
+
await assertRegularFile(privateKeyPath, 'Publisher private key');
|
|
220
|
+
const privateKey = await readFile(privateKeyPath);
|
|
221
|
+
const signature = signPackage(validated.manifestValue, validated.integrityValue, privateKey, options.passphrase === undefined ? {} : { passphrase: options.passphrase });
|
|
222
|
+
const publicKey = publisherPublicKeyFromPrivate(privateKey, options.passphrase === undefined ? {} : { passphrase: options.passphrase });
|
|
223
|
+
const files = new Map(archive.files);
|
|
224
|
+
files.set('signature.sig', Buffer.from(`${signature}\n`, 'utf8'));
|
|
225
|
+
validatePackageFiles(files, { ...compatibility, publicKey, requireSignature: true });
|
|
226
|
+
const defaultOutput = input.slice(0, -UNSIGNED_PACKAGE_EXTENSION.length) + PACKAGE_EXTENSION;
|
|
227
|
+
const output = resolve(options.output ?? defaultOutput);
|
|
228
|
+
if (!output.endsWith(PACKAGE_EXTENSION) || output.endsWith(UNSIGNED_PACKAGE_EXTENSION)) {
|
|
229
|
+
fail('sign.output-extension', `Signed package output must end with ${PACKAGE_EXTENSION}`, output);
|
|
230
|
+
}
|
|
231
|
+
if (output === input)
|
|
232
|
+
fail('sign.same-output', 'Signer output must not overwrite its unsigned input', output);
|
|
233
|
+
const written = await writePackageArchive([...files].map(([path, bytes]) => ({ path, bytes })), output, { force: options.force });
|
|
234
|
+
return Object.freeze({
|
|
235
|
+
...written,
|
|
236
|
+
pluginId: validated.manifest.id,
|
|
237
|
+
version: validated.manifest.version,
|
|
238
|
+
keyId: publisherKeyId(publicKey),
|
|
239
|
+
publicKey,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type BuildPluginProjectOptions, type BuiltPluginProject } from './project.js';
|
|
2
|
+
export declare function watchPluginProject(options: BuildPluginProjectOptions & {
|
|
3
|
+
signal: AbortSignal;
|
|
4
|
+
onBuilt: (result: BuiltPluginProject) => void;
|
|
5
|
+
onError: (error: unknown) => void;
|
|
6
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { readdir, lstat } from 'node:fs/promises';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { buildPluginProject } from './project.js';
|
|
5
|
+
const ignored = new Set(['node_modules', '.git', '.notegen', 'dist', 'build', '.next']);
|
|
6
|
+
async function sourceRevision(root) {
|
|
7
|
+
const hash = createHash('sha256');
|
|
8
|
+
let count = 0;
|
|
9
|
+
async function visit(directory, depth) {
|
|
10
|
+
if (depth > 32)
|
|
11
|
+
throw new Error('Development source tree exceeds 32 levels');
|
|
12
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
13
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
14
|
+
for (const entry of entries) {
|
|
15
|
+
if (ignored.has(entry.name))
|
|
16
|
+
continue;
|
|
17
|
+
if (++count > 10_000)
|
|
18
|
+
throw new Error('Development source tree exceeds 10,000 entries');
|
|
19
|
+
const path = join(directory, entry.name);
|
|
20
|
+
const metadata = await lstat(path);
|
|
21
|
+
if (metadata.isSymbolicLink())
|
|
22
|
+
continue;
|
|
23
|
+
if (metadata.isDirectory())
|
|
24
|
+
await visit(path, depth + 1);
|
|
25
|
+
else if (metadata.isFile())
|
|
26
|
+
hash.update(JSON.stringify([path, metadata.size, metadata.mtimeMs, metadata.ctimeMs]));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
await visit(root, 0);
|
|
30
|
+
return hash.digest('hex');
|
|
31
|
+
}
|
|
32
|
+
export async function watchPluginProject(options) {
|
|
33
|
+
const root = resolve(options.directory ?? process.cwd());
|
|
34
|
+
let previous;
|
|
35
|
+
let lastError = '';
|
|
36
|
+
while (!options.signal.aborted) {
|
|
37
|
+
try {
|
|
38
|
+
const revision = await sourceRevision(root);
|
|
39
|
+
if (revision !== previous) {
|
|
40
|
+
previous = revision;
|
|
41
|
+
const built = await buildPluginProject(options);
|
|
42
|
+
lastError = '';
|
|
43
|
+
if (!options.signal.aborted)
|
|
44
|
+
options.onBuilt(built);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
49
|
+
if (lastError !== message)
|
|
50
|
+
options.onError(error);
|
|
51
|
+
lastError = message;
|
|
52
|
+
}
|
|
53
|
+
if (options.signal.aborted)
|
|
54
|
+
break;
|
|
55
|
+
await new Promise(resolveWait => {
|
|
56
|
+
const finish = () => { clearTimeout(timer); options.signal.removeEventListener('abort', finish); resolveWait(); };
|
|
57
|
+
const timer = setTimeout(finish, 1_000);
|
|
58
|
+
options.signal.addEventListener('abort', finish, { once: true });
|
|
59
|
+
if (options.signal.aborted)
|
|
60
|
+
finish();
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@notegen/plugin-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Create, validate, build, package, sign, and verify NoteGen plugins.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"bin": {
|
|
9
|
+
"notegen-plugin": "./dist/bin.js"
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public",
|
|
28
|
+
"provenance": true
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/codexu/note-gen-plugin-sdk.git",
|
|
33
|
+
"directory": "packages/plugin-cli"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://notegen.top",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/codexu/note-gen-plugin-sdk/issues"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"canonicalize": "^4.0.0",
|
|
41
|
+
"commander": "^14.0.3",
|
|
42
|
+
"es-module-lexer": "^3.0.2",
|
|
43
|
+
"esbuild": "^0.28.2",
|
|
44
|
+
"jsonc-parser": "^3.3.1",
|
|
45
|
+
"semver": "^7.8.5",
|
|
46
|
+
"yauzl": "^3.4.0",
|
|
47
|
+
"yazl": "^3.3.1",
|
|
48
|
+
"@notegen/plugin-api": "^0.1.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^20.19.43",
|
|
52
|
+
"@types/semver": "^7.8.0",
|
|
53
|
+
"@types/yauzl": "^3.4.0",
|
|
54
|
+
"@types/yazl": "^3.3.1",
|
|
55
|
+
"typescript": "^5.8.3"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=20"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
62
|
+
"build": "pnpm run clean && tsc -p tsconfig.json"
|
|
63
|
+
}
|
|
64
|
+
}
|