@nmakarov/cli-toolkit 0.27.0 → 0.32.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/README.md +38 -0
- package/dist/aws.cjs +210 -0
- package/dist/aws.cjs.map +1 -0
- package/dist/aws.js +201 -0
- package/dist/aws.js.map +1 -0
- package/dist/cli-runner.cjs +43 -11
- package/dist/cli-runner.cjs.map +1 -1
- package/dist/cli-runner.js +43 -11
- package/dist/cli-runner.js.map +1 -1
- package/dist/deploy.cjs +1166 -0
- package/dist/deploy.cjs.map +1 -0
- package/dist/deploy.js +1096 -0
- package/dist/deploy.js.map +1 -0
- package/dist/index.cjs +1405 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1354 -29
- package/dist/index.js.map +1 -1
- package/dist/init.cjs +19 -6
- package/dist/init.cjs.map +1 -1
- package/dist/init.js +19 -6
- package/dist/init.js.map +1 -1
- package/dist/params.cjs +8 -4
- package/dist/params.cjs.map +1 -1
- package/dist/params.js +8 -4
- package/dist/params.js.map +1 -1
- package/dist/s3.cjs +1 -0
- package/dist/s3.cjs.map +1 -1
- package/dist/s3.js +1 -0
- package/dist/s3.js.map +1 -1
- package/dist/tasks.cjs +25 -7
- package/dist/tasks.cjs.map +1 -1
- package/dist/tasks.js +25 -7
- package/dist/tasks.js.map +1 -1
- package/package.json +15 -2
- package/scripts/deploy/cli.js +181 -0
package/dist/s3.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/s3/index.js","../src/errors.js"],"sourcesContent":["/**\n * S3 component\n *\n * Thin async wrapper over @aws-sdk/client-s3 v3 with the cli-toolkit conventions:\n * const s3 = await S3.init(context);\n *\n * Supports multiple \"profiles\" (local, prod, stage, …) selected at runtime via\n * `--bucket=<profile>` (or `S3_BUCKET=<profile>`). Each profile has its own set\n * of env-suffixed parameters resolved by the framework, mirroring how `Db.init`\n * uses `dbName` to look up `dbConnectionString${Name}`.\n *\n * Profile selector:\n * --bucket=local (default)\n * --bucket=prod\n * --bucket=stage\n *\n * Per-profile parameters (all read via context.params; resolved from CLI,\n * env, .env files, defaults — see @nmakarov/cli-toolkit/params docs):\n * s3Bucket{Profile} string required\n * s3Region{Profile} string default us-east-1\n * s3Endpoint{Profile} string optional (set for MinIO/local; unset = real AWS)\n * s3ForcePathStyle{Profile} boolean default false (true for MinIO)\n * s3AccessKeyId{Profile} string optional (omit on EC2 to use instance profile)\n * s3SecretAccessKey{Profile} string optional\n *\n * Equivalent env vars are SCREAMING_SNAKE_CASE with the profile suffix:\n * S3_BUCKET_LOCAL, S3_REGION_LOCAL, S3_ENDPOINT_LOCAL,\n * S3_FORCE_PATH_STYLE_LOCAL, S3_ACCESS_KEY_ID_LOCAL, S3_SECRET_ACCESS_KEY_LOCAL\n *\n * Example (local with MinIO from docker-compose):\n * S3_BUCKET_LOCAL=photos-local\n * S3_REGION_LOCAL=us-east-1\n * S3_ENDPOINT_LOCAL=http://localhost:9000\n * S3_FORCE_PATH_STYLE_LOCAL=true\n * S3_ACCESS_KEY_ID_LOCAL=dev\n * S3_SECRET_ACCESS_KEY_LOCAL=devdevdev\n */\n\nimport {\n S3Client,\n HeadBucketCommand,\n HeadObjectCommand,\n GetObjectCommand,\n PutObjectCommand,\n DeleteObjectCommand,\n CopyObjectCommand,\n ListObjectsV2Command,\n PutObjectTaggingCommand,\n GetObjectTaggingCommand,\n} from \"@aws-sdk/client-s3\";\n\nimport { ParamError } from \"../errors.js\";\n\nconst DEFAULT_PROFILE = \"local\";\n\nfunction capitalize(str) {\n if (!str) return str;\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\nexport class S3 {\n /**\n * Build an S3 instance. Reads bucket profile from --bucket (default \"local\"),\n * then resolves per-profile params, builds the SDK client, and returns the\n * instance. Optionally pings the bucket once to verify reachability.\n */\n static async init(context, options = {}) {\n const profileDef = { bucket: \"string\" };\n const discovered = context?.params?.getAllForModule?.(\"s3\", profileDef) ?? {};\n const profile = options.bucket ?? discovered.bucket ?? DEFAULT_PROFILE;\n const cap = capitalize(profile);\n\n const config = {\n profile,\n bucketName: options.bucketName\n ?? await context.params.get(`s3Bucket${cap}`, \"string\"),\n region: options.region\n ?? await context.params.get(`s3Region${cap}`, \"string default us-east-1\"),\n endpoint: options.endpoint\n ?? await context.params.get(`s3Endpoint${cap}`, \"string\"),\n forcePathStyle: options.forcePathStyle\n ?? await context.params.get(`s3ForcePathStyle${cap}`, \"boolean default false\"),\n accessKeyId: options.accessKeyId\n ?? await context.params.get(`s3AccessKeyId${cap}`, \"string\"),\n secretAccessKey: options.secretAccessKey\n ?? await context.params.get(`s3SecretAccessKey${cap}`, \"string\"),\n };\n\n if (!config.bucketName) {\n throw new ParamError(\n `S3: bucket name not configured for profile \"${profile}\" `\n + `(set s3Bucket${cap} or S3_BUCKET_${profile.toUpperCase()})`\n );\n }\n\n const s3 = new S3(context, config);\n\n if (options.testBucket !== false) {\n try {\n await s3.bucketExists();\n context.logger?.debug?.(\n `[S3] profile=\"${profile}\" bucket=\"${config.bucketName}\" reachable`\n );\n } catch (err) {\n context.logger?.warn?.(\n `[S3] profile=\"${profile}\" bucket=\"${config.bucketName}\" `\n + `reachability test failed: ${err?.message ?? err}`\n );\n }\n }\n\n return s3;\n }\n\n constructor(context, config) {\n this.logger = context?.logger ?? console;\n this.profile = config.profile;\n this.bucketName = config.bucketName;\n this.region = config.region;\n this.endpoint = config.endpoint || null;\n\n const clientConfig = { region: config.region };\n if (config.endpoint) clientConfig.endpoint = config.endpoint;\n if (config.forcePathStyle) clientConfig.forcePathStyle = true;\n if (config.accessKeyId && config.secretAccessKey) {\n clientConfig.credentials = {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n };\n }\n\n this.client = new S3Client(clientConfig);\n }\n\n // ── info ────────────────────────────────────────────────────────────────\n getBucketName() { return this.bucketName; }\n getProfile() { return this.profile; }\n getRegion() { return this.region; }\n getEndpoint() { return this.endpoint; }\n\n // ── reachability ────────────────────────────────────────────────────────\n async bucketExists() {\n await this.client.send(new HeadBucketCommand({ Bucket: this.bucketName }));\n return true;\n }\n\n // ── HEAD / GET ──────────────────────────────────────────────────────────\n /** Returns null on 404; never throws for \"missing\". Other errors throw. */\n async headObject(key) {\n try {\n const out = await this.client.send(new HeadObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n }));\n return {\n etag: out.ETag,\n size: out.ContentLength,\n contentType: out.ContentType,\n lastModified: out.LastModified,\n metadata: out.Metadata,\n storageClass: out.StorageClass,\n };\n } catch (err) {\n if (this._isNotFound(err)) return null;\n throw err;\n }\n }\n\n /** Returns { body: Readable, contentType, contentLength, etag, ... } or null on 404. */\n async getObject(key) {\n try {\n const out = await this.client.send(new GetObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n }));\n return {\n body: out.Body,\n contentType: out.ContentType,\n contentLength: out.ContentLength,\n etag: out.ETag,\n lastModified: out.LastModified,\n metadata: out.Metadata,\n };\n } catch (err) {\n if (this._isNotFound(err)) return null;\n throw err;\n }\n }\n\n /** Buffers the whole object. Use only for small objects (manifests, JSON). */\n async getObjectBytes(key) {\n const obj = await this.getObject(key);\n if (!obj) return null;\n const chunks = [];\n for await (const chunk of obj.body) chunks.push(chunk);\n return { ...obj, body: Buffer.concat(chunks) };\n }\n\n /** Convenience for JSON manifests. Returns parsed object or null on 404. */\n async getJson(key) {\n const obj = await this.getObjectBytes(key);\n if (!obj) return null;\n return JSON.parse(obj.body.toString(\"utf8\"));\n }\n\n // ── PUT ─────────────────────────────────────────────────────────────────\n async putObject({ key, body, contentType, contentLength, tags, metadata }) {\n const cmd = new PutObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n Body: body,\n ...(contentType && { ContentType: contentType }),\n ...(contentLength != null && { ContentLength: contentLength }),\n ...(metadata && { Metadata: metadata }),\n ...(tags && { Tagging: this._tagsToQuery(tags) }),\n });\n return this.client.send(cmd);\n }\n\n /** Convenience for JSON manifests. */\n async putJson(key, value, opts = {}) {\n const json = JSON.stringify(value, null, opts.pretty ? 2 : 0);\n const body = Buffer.from(json, \"utf8\");\n return this.putObject({\n key,\n body,\n contentType: \"application/json\",\n contentLength: body.length,\n tags: opts.tags,\n metadata: opts.metadata,\n });\n }\n\n // ── DELETE ──────────────────────────────────────────────────────────────\n async deleteObject(key) {\n return this.client.send(new DeleteObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n }));\n }\n\n // ── COPY (for migration: legacy → new bucket, or intra-bucket \"rename\") ─\n async copyObject({ sourceBucket, sourceKey, key, contentType, metadata, tags }) {\n const src = sourceBucket || this.bucketName;\n const cmd = new CopyObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n CopySource: encodeURIComponent(`${src}/${sourceKey}`),\n ...(contentType && {\n ContentType: contentType,\n MetadataDirective: \"REPLACE\",\n }),\n ...(metadata && {\n Metadata: metadata,\n MetadataDirective: \"REPLACE\",\n }),\n ...(tags && {\n Tagging: this._tagsToQuery(tags),\n TaggingDirective: \"REPLACE\",\n }),\n });\n return this.client.send(cmd);\n }\n\n // ── LIST ────────────────────────────────────────────────────────────────\n async listObjects(prefix, { keysOnly = false, maxKeys = 1000, continuationToken } = {}) {\n const out = await this.client.send(new ListObjectsV2Command({\n Bucket: this.bucketName,\n Prefix: prefix,\n MaxKeys: maxKeys,\n ContinuationToken: continuationToken,\n }));\n const items = (out.Contents ?? []).map(o => ({\n key: o.Key,\n size: o.Size,\n etag: o.ETag,\n lastModified: o.LastModified,\n storageClass: o.StorageClass,\n }));\n return {\n items: keysOnly ? items.map(i => i.key) : items,\n isTruncated: !!out.IsTruncated,\n nextContinuationToken: out.NextContinuationToken,\n };\n }\n\n // ── TAGS (used for lifecycle rules, e.g. status=closed → Glacier IR) ────\n async putObjectTagging(key, tags) {\n return this.client.send(new PutObjectTaggingCommand({\n Bucket: this.bucketName,\n Key: key,\n Tagging: { TagSet: this._tagsToTagSet(tags) },\n }));\n }\n\n async getObjectTagging(key) {\n const out = await this.client.send(new GetObjectTaggingCommand({\n Bucket: this.bucketName,\n Key: key,\n }));\n const tags = {};\n for (const t of out.TagSet ?? []) tags[t.Key] = t.Value;\n return tags;\n }\n\n // ── internals ───────────────────────────────────────────────────────────\n _isNotFound(err) {\n const status = err?.$metadata?.httpStatusCode;\n return status === 404\n || err?.name === \"NotFound\"\n || err?.name === \"NoSuchKey\"\n || err?.Code === \"NoSuchKey\";\n }\n\n _tagsToQuery(tags) {\n return Object.entries(tags)\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)\n .join(\"&\");\n }\n\n _tagsToTagSet(tags) {\n return Object.entries(tags).map(([Key, Value]) => ({ Key, Value: String(Value) }));\n }\n}\n","/**\n * Error classes for the CLI toolkit\n */\n\nexport class FrameworkError extends Error {\n constructor(message) {\n super(message);\n this.name = \"FrameworkError\";\n }\n}\n\nexport class ParamError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"ParamError\";\n }\n}\n\nexport class InitError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"InitError\";\n }\n}\n\nexport class CriticalRequestError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"CriticalRequestError\";\n }\n}\n\nexport class ControlFlowError extends Error {\n constructor(message) {\n super(message);\n this.name = \"ControlFlowError\";\n }\n}\n\nexport class HttpClientError extends FrameworkError {\n constructor(message, cause) {\n super(message);this.cause = cause;;\n this.name = \"HttpClientError\";\n }\n}\n\nexport class FileDatabaseError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"FileDatabaseError\";\n }\n}\n\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/s3/index.js","../src/errors.js"],"sourcesContent":["/**\n * S3 component\n *\n * Thin async wrapper over @aws-sdk/client-s3 v3 with the cli-toolkit conventions:\n * const s3 = await S3.init(context);\n *\n * Supports multiple \"profiles\" (local, prod, stage, …) selected at runtime via\n * `--bucket=<profile>` (or `S3_BUCKET=<profile>`). Each profile has its own set\n * of env-suffixed parameters resolved by the framework, mirroring how `Db.init`\n * uses `dbName` to look up `dbConnectionString${Name}`.\n *\n * Profile selector:\n * --bucket=local (default)\n * --bucket=prod\n * --bucket=stage\n *\n * Per-profile parameters (all read via context.params; resolved from CLI,\n * env, .env files, defaults — see @nmakarov/cli-toolkit/params docs):\n * s3Bucket{Profile} string required\n * s3Region{Profile} string default us-east-1\n * s3Endpoint{Profile} string optional (set for MinIO/local; unset = real AWS)\n * s3ForcePathStyle{Profile} boolean default false (true for MinIO)\n * s3AccessKeyId{Profile} string optional (omit on EC2 to use instance profile)\n * s3SecretAccessKey{Profile} string optional\n *\n * Equivalent env vars are SCREAMING_SNAKE_CASE with the profile suffix:\n * S3_BUCKET_LOCAL, S3_REGION_LOCAL, S3_ENDPOINT_LOCAL,\n * S3_FORCE_PATH_STYLE_LOCAL, S3_ACCESS_KEY_ID_LOCAL, S3_SECRET_ACCESS_KEY_LOCAL\n *\n * Example (local with MinIO from docker-compose):\n * S3_BUCKET_LOCAL=photos-local\n * S3_REGION_LOCAL=us-east-1\n * S3_ENDPOINT_LOCAL=http://localhost:9000\n * S3_FORCE_PATH_STYLE_LOCAL=true\n * S3_ACCESS_KEY_ID_LOCAL=dev\n * S3_SECRET_ACCESS_KEY_LOCAL=devdevdev\n */\n\nimport {\n S3Client,\n HeadBucketCommand,\n HeadObjectCommand,\n GetObjectCommand,\n PutObjectCommand,\n DeleteObjectCommand,\n CopyObjectCommand,\n ListObjectsV2Command,\n PutObjectTaggingCommand,\n GetObjectTaggingCommand,\n} from \"@aws-sdk/client-s3\";\n\nimport { ParamError } from \"../errors.js\";\n\nconst DEFAULT_PROFILE = \"local\";\n\nfunction capitalize(str) {\n if (!str) return str;\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\nexport class S3 {\n /**\n * Build an S3 instance. Reads bucket profile from --bucket (default \"local\"),\n * then resolves per-profile params, builds the SDK client, and returns the\n * instance. Optionally pings the bucket once to verify reachability.\n */\n static async init(context, options = {}) {\n const profileDef = { bucket: \"string\" };\n const discovered = context?.params?.getAllForModule?.(\"s3\", profileDef) ?? {};\n const profile = options.bucket ?? discovered.bucket ?? DEFAULT_PROFILE;\n const cap = capitalize(profile);\n\n const config = {\n profile,\n bucketName: options.bucketName\n ?? await context.params.get(`s3Bucket${cap}`, \"string\"),\n region: options.region\n ?? await context.params.get(`s3Region${cap}`, \"string default us-east-1\"),\n endpoint: options.endpoint\n ?? await context.params.get(`s3Endpoint${cap}`, \"string\"),\n forcePathStyle: options.forcePathStyle\n ?? await context.params.get(`s3ForcePathStyle${cap}`, \"boolean default false\"),\n accessKeyId: options.accessKeyId\n ?? await context.params.get(`s3AccessKeyId${cap}`, \"string\"),\n secretAccessKey: options.secretAccessKey\n ?? await context.params.get(`s3SecretAccessKey${cap}`, \"string\"),\n };\n\n if (!config.bucketName) {\n throw new ParamError(\n `S3: bucket name not configured for profile \"${profile}\" `\n + `(set s3Bucket${cap} or S3_BUCKET_${profile.toUpperCase()})`\n );\n }\n\n const s3 = new S3(context, config);\n\n if (options.testBucket !== false) {\n try {\n await s3.bucketExists();\n context.logger?.debug?.(\n `[S3] profile=\"${profile}\" bucket=\"${config.bucketName}\" reachable`\n );\n } catch (err) {\n context.logger?.warn?.(\n `[S3] profile=\"${profile}\" bucket=\"${config.bucketName}\" `\n + `reachability test failed: ${err?.message ?? err}`\n );\n }\n }\n\n return s3;\n }\n\n constructor(context, config) {\n this.logger = context?.logger ?? console;\n this.profile = config.profile;\n this.bucketName = config.bucketName;\n this.region = config.region;\n this.endpoint = config.endpoint || null;\n\n const clientConfig = { region: config.region };\n if (config.endpoint) clientConfig.endpoint = config.endpoint;\n if (config.forcePathStyle) clientConfig.forcePathStyle = true;\n if (config.accessKeyId && config.secretAccessKey) {\n clientConfig.credentials = {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n };\n }\n\n // Silence the AWS SDK v3 \"node >=22 required after Jan 2027\" notice on\n // older Node. Honors an explicit env value if the user already set one.\n process.env.AWS_SDK_JS_NODE_VERSION_SUPPORT_WARNING_DISABLED ??= \"true\";\n\n this.client = new S3Client(clientConfig);\n }\n\n // ── info ────────────────────────────────────────────────────────────────\n getBucketName() { return this.bucketName; }\n getProfile() { return this.profile; }\n getRegion() { return this.region; }\n getEndpoint() { return this.endpoint; }\n\n // ── reachability ────────────────────────────────────────────────────────\n async bucketExists() {\n await this.client.send(new HeadBucketCommand({ Bucket: this.bucketName }));\n return true;\n }\n\n // ── HEAD / GET ──────────────────────────────────────────────────────────\n /** Returns null on 404; never throws for \"missing\". Other errors throw. */\n async headObject(key) {\n try {\n const out = await this.client.send(new HeadObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n }));\n return {\n etag: out.ETag,\n size: out.ContentLength,\n contentType: out.ContentType,\n lastModified: out.LastModified,\n metadata: out.Metadata,\n storageClass: out.StorageClass,\n };\n } catch (err) {\n if (this._isNotFound(err)) return null;\n throw err;\n }\n }\n\n /** Returns { body: Readable, contentType, contentLength, etag, ... } or null on 404. */\n async getObject(key) {\n try {\n const out = await this.client.send(new GetObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n }));\n return {\n body: out.Body,\n contentType: out.ContentType,\n contentLength: out.ContentLength,\n etag: out.ETag,\n lastModified: out.LastModified,\n metadata: out.Metadata,\n };\n } catch (err) {\n if (this._isNotFound(err)) return null;\n throw err;\n }\n }\n\n /** Buffers the whole object. Use only for small objects (manifests, JSON). */\n async getObjectBytes(key) {\n const obj = await this.getObject(key);\n if (!obj) return null;\n const chunks = [];\n for await (const chunk of obj.body) chunks.push(chunk);\n return { ...obj, body: Buffer.concat(chunks) };\n }\n\n /** Convenience for JSON manifests. Returns parsed object or null on 404. */\n async getJson(key) {\n const obj = await this.getObjectBytes(key);\n if (!obj) return null;\n return JSON.parse(obj.body.toString(\"utf8\"));\n }\n\n // ── PUT ─────────────────────────────────────────────────────────────────\n async putObject({ key, body, contentType, contentLength, tags, metadata }) {\n const cmd = new PutObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n Body: body,\n ...(contentType && { ContentType: contentType }),\n ...(contentLength != null && { ContentLength: contentLength }),\n ...(metadata && { Metadata: metadata }),\n ...(tags && { Tagging: this._tagsToQuery(tags) }),\n });\n return this.client.send(cmd);\n }\n\n /** Convenience for JSON manifests. */\n async putJson(key, value, opts = {}) {\n const json = JSON.stringify(value, null, opts.pretty ? 2 : 0);\n const body = Buffer.from(json, \"utf8\");\n return this.putObject({\n key,\n body,\n contentType: \"application/json\",\n contentLength: body.length,\n tags: opts.tags,\n metadata: opts.metadata,\n });\n }\n\n // ── DELETE ──────────────────────────────────────────────────────────────\n async deleteObject(key) {\n return this.client.send(new DeleteObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n }));\n }\n\n // ── COPY (for migration: legacy → new bucket, or intra-bucket \"rename\") ─\n async copyObject({ sourceBucket, sourceKey, key, contentType, metadata, tags }) {\n const src = sourceBucket || this.bucketName;\n const cmd = new CopyObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n CopySource: encodeURIComponent(`${src}/${sourceKey}`),\n ...(contentType && {\n ContentType: contentType,\n MetadataDirective: \"REPLACE\",\n }),\n ...(metadata && {\n Metadata: metadata,\n MetadataDirective: \"REPLACE\",\n }),\n ...(tags && {\n Tagging: this._tagsToQuery(tags),\n TaggingDirective: \"REPLACE\",\n }),\n });\n return this.client.send(cmd);\n }\n\n // ── LIST ────────────────────────────────────────────────────────────────\n async listObjects(prefix, { keysOnly = false, maxKeys = 1000, continuationToken } = {}) {\n const out = await this.client.send(new ListObjectsV2Command({\n Bucket: this.bucketName,\n Prefix: prefix,\n MaxKeys: maxKeys,\n ContinuationToken: continuationToken,\n }));\n const items = (out.Contents ?? []).map(o => ({\n key: o.Key,\n size: o.Size,\n etag: o.ETag,\n lastModified: o.LastModified,\n storageClass: o.StorageClass,\n }));\n return {\n items: keysOnly ? items.map(i => i.key) : items,\n isTruncated: !!out.IsTruncated,\n nextContinuationToken: out.NextContinuationToken,\n };\n }\n\n // ── TAGS (used for lifecycle rules, e.g. status=closed → Glacier IR) ────\n async putObjectTagging(key, tags) {\n return this.client.send(new PutObjectTaggingCommand({\n Bucket: this.bucketName,\n Key: key,\n Tagging: { TagSet: this._tagsToTagSet(tags) },\n }));\n }\n\n async getObjectTagging(key) {\n const out = await this.client.send(new GetObjectTaggingCommand({\n Bucket: this.bucketName,\n Key: key,\n }));\n const tags = {};\n for (const t of out.TagSet ?? []) tags[t.Key] = t.Value;\n return tags;\n }\n\n // ── internals ───────────────────────────────────────────────────────────\n _isNotFound(err) {\n const status = err?.$metadata?.httpStatusCode;\n return status === 404\n || err?.name === \"NotFound\"\n || err?.name === \"NoSuchKey\"\n || err?.Code === \"NoSuchKey\";\n }\n\n _tagsToQuery(tags) {\n return Object.entries(tags)\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)\n .join(\"&\");\n }\n\n _tagsToTagSet(tags) {\n return Object.entries(tags).map(([Key, Value]) => ({ Key, Value: String(Value) }));\n }\n}\n","/**\n * Error classes for the CLI toolkit\n */\n\nexport class FrameworkError extends Error {\n constructor(message) {\n super(message);\n this.name = \"FrameworkError\";\n }\n}\n\nexport class ParamError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"ParamError\";\n }\n}\n\nexport class InitError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"InitError\";\n }\n}\n\nexport class CriticalRequestError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"CriticalRequestError\";\n }\n}\n\nexport class ControlFlowError extends Error {\n constructor(message) {\n super(message);\n this.name = \"ControlFlowError\";\n }\n}\n\nexport class HttpClientError extends FrameworkError {\n constructor(message, cause) {\n super(message);this.cause = cause;;\n this.name = \"HttpClientError\";\n }\n}\n\nexport class FileDatabaseError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"FileDatabaseError\";\n }\n}\n\n"],"mappings":"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","names":[]}
|
package/dist/tasks.cjs
CHANGED
|
@@ -259,7 +259,7 @@ function defineTasksTable(t, db, tableNameForIndex) {
|
|
|
259
259
|
t.timestamp("past_due").defaultTo(null);
|
|
260
260
|
t.text("name").notNullable();
|
|
261
261
|
t.text("opid");
|
|
262
|
-
t.
|
|
262
|
+
t.jsonb("params");
|
|
263
263
|
t.text("service_group");
|
|
264
264
|
t.integer("instance_number");
|
|
265
265
|
t.text("service_name");
|
|
@@ -268,7 +268,7 @@ function defineTasksTable(t, db, tableNameForIndex) {
|
|
|
268
268
|
t.timestamp("status_changed_at").defaultTo(null);
|
|
269
269
|
t.text("progress");
|
|
270
270
|
t.boolean("success");
|
|
271
|
-
t.
|
|
271
|
+
t.jsonb("results");
|
|
272
272
|
t.index(["service_group", "status", "priority", "created_at"], `${tableNameForIndex}_claim_idx`);
|
|
273
273
|
t.index(["service_group", "name"], `${tableNameForIndex}_group_name_idx`);
|
|
274
274
|
}
|
|
@@ -283,11 +283,29 @@ function taskHistoryInsertFromQueueRow(row, overrides) {
|
|
|
283
283
|
async function ensureTaskTables(context, options = {}) {
|
|
284
284
|
const queueName = options.queueName ?? "tasks";
|
|
285
285
|
const recreate = options.recreate ?? false;
|
|
286
|
+
const dryRun = options.dryRun ?? false;
|
|
286
287
|
const db = getDb(context);
|
|
288
|
+
const log = context.logger ?? console;
|
|
287
289
|
const { tasksTable, historyTable, registryTable } = queueToTableNames(queueName);
|
|
288
290
|
const needsTasks = recreate ? true : !await db.tableExists(tasksTable);
|
|
289
291
|
const needsHistory = recreate ? true : !await db.tableExists(historyTable);
|
|
290
292
|
const needsRegistry = recreate ? true : !await db.tableExists(registryTable);
|
|
293
|
+
if (dryRun) {
|
|
294
|
+
const plan = [];
|
|
295
|
+
if (recreate) {
|
|
296
|
+
plan.push(`DROP TABLE IF EXISTS ${historyTable}, ${tasksTable}, ${registryTable}`);
|
|
297
|
+
}
|
|
298
|
+
if (needsTasks) plan.push(`CREATE TABLE ${tasksTable} (tasks queue)`);
|
|
299
|
+
if (needsHistory) plan.push(`CREATE TABLE ${historyTable} (history mirror)`);
|
|
300
|
+
if (needsRegistry) plan.push(`CREATE TABLE ${registryTable} (services registry)`);
|
|
301
|
+
if (plan.length === 0) {
|
|
302
|
+
log.info?.(`[tasks-schema] dryRun \u2014 queue "${queueName}" already up to date; no DDL`);
|
|
303
|
+
} else {
|
|
304
|
+
log.info?.(`[tasks-schema] dryRun \u2014 would run ${plan.length} statement(s) for queue "${queueName}":`);
|
|
305
|
+
for (const s of plan) log.info?.(` - ${s}`);
|
|
306
|
+
}
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
291
309
|
if (recreate) {
|
|
292
310
|
await db.schema.dropTableIfExists(historyTable);
|
|
293
311
|
await db.schema.dropTableIfExists(tasksTable);
|
|
@@ -2781,8 +2799,7 @@ function forwardChildLogToParent(context, prefix, message) {
|
|
|
2781
2799
|
}
|
|
2782
2800
|
function buildNodeArgs(scriptPath, cliArgs) {
|
|
2783
2801
|
const inheritedExecArgs = Array.isArray(process.execArgv) ? [...process.execArgv] : [];
|
|
2784
|
-
|
|
2785
|
-
return hasTsRuntimeInParent ? [...inheritedExecArgs, scriptPath, ...cliArgs] : ["--import", "tsx", scriptPath, ...cliArgs];
|
|
2802
|
+
return [...inheritedExecArgs, scriptPath, ...cliArgs];
|
|
2786
2803
|
}
|
|
2787
2804
|
function resolveTasksTableName(context) {
|
|
2788
2805
|
return context.tasksQueueName || context.params?.get?.("table") || "tasks";
|
|
@@ -3022,9 +3039,8 @@ async function executeClaimedTask(context, tasksTable, historyTable, row, regist
|
|
|
3022
3039
|
const dbName = String(context?.params?.get?.("dbName") || "local");
|
|
3023
3040
|
const tableName = String(context?.params?.get?.("table") || "tasks");
|
|
3024
3041
|
const fallbackRecoverCommand = [
|
|
3025
|
-
"
|
|
3026
|
-
"
|
|
3027
|
-
"examples/tasks/recover-task.ts",
|
|
3042
|
+
"node",
|
|
3043
|
+
"examples/tasks/recover-task.js",
|
|
3028
3044
|
`--dbName='${dbName.replace(/'/g, `'\\''`)}'`,
|
|
3029
3045
|
`--table='${tableName.replace(/'/g, `'\\''`)}'`,
|
|
3030
3046
|
`--id='${String(row.id).replace(/'/g, `'\\''`)}'`
|
|
@@ -3090,6 +3106,8 @@ async function claimNextRunnableTask(context, tasksTable, serviceGroup, registry
|
|
|
3090
3106
|
const db = getDb3(context);
|
|
3091
3107
|
let query = db(tasksTable).where({ status: "idle" }).where(function() {
|
|
3092
3108
|
this.whereNull("service_group").orWhere({ service_group: serviceGroup });
|
|
3109
|
+
}).where(function() {
|
|
3110
|
+
this.whereNull("next_run_at").orWhere("next_run_at", "<=", db.fn.now());
|
|
3093
3111
|
}).orderByRaw("CASE WHEN past_due IS NULL THEN 1 ELSE 0 END ASC").orderBy([{ column: "priority", order: "asc" }]).orderByRaw("CASE WHEN completed_at IS NULL THEN 0 ELSE 1 END ASC").orderBy([{ column: "completed_at", order: "asc" }, { column: "created_at", order: "asc" }]).limit(scanLimit);
|
|
3094
3112
|
if (taskNames && taskNames.length > 0) {
|
|
3095
3113
|
query = query.whereIn("name", taskNames);
|